from ctypes import *
import os
import platform
from typing import List, Callable
from dataclasses import dataclass
import threading
import time

@dataclass
class Tag:
    """Represents a UHF RFID tag."""
    antenna_id: int
    epc: str
    rssi: int
    timestamp: float

class UHFReader:
    def __init__(self):
        self.lib = None
        self.handle = None
        self._load_library()
        self._inventory_callback = None
        self._inventory_thread = None
        self._inventory_running = False
        
    def _load_library(self):
        """Load the RFID reader shared library."""
        lib_path = os.path.join(
            os.path.dirname(os.path.dirname(__file__)),
            "rfid_demo_qt_v1.2.2",
            "MyBuild",
            "x86_64",
            "libGZAE_rfid.so"
        )
        if not os.path.exists(lib_path):
            raise FileNotFoundError(f"Library not found at {lib_path}")
        
        self.lib = CDLL(lib_path)
        
        # Define function prototypes
        self.lib.RDR_Open.restype = c_void_p
        self.lib.RDR_Open.argtypes = [c_char_p]
        
        self.lib.RDR_Close.argtypes = [c_void_p]
        
        self.lib.RDR_GetReaderInfor.argtypes = [c_void_p, c_char_p, POINTER(c_int)]
        
        self.lib.RDR_TagInventory.argtypes = [c_void_p, c_void_p]
        
        self.lib.RDR_GetTagDataReport.argtypes = [c_void_p, POINTER(c_int)]
        self.lib.RDR_GetTagDataReport.restype = c_void_p
        
        self.lib.RDR_ParseTagDataReportRaw.argtypes = [c_void_p, c_void_p, POINTER(c_byte), 
                                                      POINTER(c_int), POINTER(c_int), POINTER(c_int)]
        
        self.lib.RDR_TagConnect.argtypes = [c_void_p, POINTER(c_byte), c_int]
        self.lib.RDR_TagConnect.restype = c_int
        
        self.lib.RDR_TagDisconnect.argtypes = [c_void_p]
        self.lib.RDR_TagDisconnect.restype = c_int
        
        self.lib.RDR_ConfigBlockRead.argtypes = [c_void_p, c_byte, c_uint, c_uint]
        
        self.lib.RDR_ConfigBlockWrite.argtypes = [c_void_p, c_byte, c_uint, c_uint, POINTER(c_byte), c_uint]
        
        self.lib.RDR_ConfigBlockLock.argtypes = [c_void_p, c_uint]
        
        self.lib.RDR_TagAccess.argtypes = [c_void_p, c_uint]
        self.lib.RDR_TagAccess.restype = c_int
    
    def open(self, conn_str: str) -> bool:
        """Open connection to the RFID reader."""
        if not self.lib:
            return False
        
        conn_str_bytes = conn_str.encode('utf-8')
        self.handle = self.lib.RDR_Open(c_char_p(conn_str_bytes))
        
        if self.handle:
            # Open RF transmitter after connecting
            self.lib.RDR_OpenRFTransmitter(self.handle)
            return True
        return False
    
    def close(self) -> bool:
        """Close connection to the RFID reader."""
        if self.handle:
            self.lib.RDR_CloseRFTransmitter(self.handle)
            self.lib.RDR_Close(self.handle)
            self.handle = None
            return True
        return False
    
    def get_reader_info(self) -> str:
        """Get reader device information."""
        if not self.handle:
            return None
            
        info_buffer = create_string_buffer(1024)
        info_len = c_int(1024)
        
        result = self.lib.RDR_GetReaderInfor(
            self.handle,
            info_buffer,
            byref(info_len)
        )
        
        if result == 0:
            return info_buffer.value.decode('utf-8')
        return None

    def _inventory_worker(self):
        """Worker thread for inventory operation."""
        while self._inventory_running:
            # Perform inventory
            result = self.lib.RDR_TagInventory(self.handle, None)  # None for default parameters
            if result == 0:
                # Get report count
                count = c_int(0)
                report = self.lib.RDR_GetTagDataReport(self.handle, byref(count))
                if report and count.value > 0:
                    tags = []
                    for i in range(count.value):
                        # Parse tag data
                        antenna_id = c_int(0)
                        rssi = c_int(0)
                        epc_len = c_int(0)
                        epc_buffer = (c_byte * 64)()  # Max EPC length is typically 64 bytes
                        
                        if self.lib.RDR_ParseTagDataReportRaw(self.handle, report, 
                                                            epc_buffer, byref(epc_len),
                                                            byref(antenna_id), byref(rssi)) == 0:
                            epc = bytes(epc_buffer[:epc_len.value]).hex()
                            tag = Tag(
                                antenna_id=antenna_id.value,
                                epc=epc,
                                rssi=rssi.value,
                                timestamp=time.time()
                            )
                            tags.append(tag)
                    
                    if self._inventory_callback and tags:
                        self._inventory_callback(tags)
            
            time.sleep(0.1)  # Small delay to prevent too frequent polling

    def start_inventory(self, antennas=None) -> bool:
        """Start inventory operation."""
        if not self.handle or self._inventory_running:
            return False
        
        self._inventory_running = True
        self._inventory_thread = threading.Thread(target=self._inventory_worker)
        self._inventory_thread.daemon = True
        self._inventory_thread.start()
        return True
    
    def stop_inventory(self):
        """Stop inventory operation."""
        if self.handle and self._inventory_running:
            self._inventory_running = False
            if self._inventory_thread:
                self._inventory_thread.join()
                self._inventory_thread = None

    def set_inventory_callback(self, callback: Callable[[List[Tag]], None]):
        """Set callback function to receive inventory results."""
        self._inventory_callback = callback

    def _connect_tag(self, epc: str) -> bool:
        """Connect to a specific tag."""
        if not self.handle:
            return False
            
        epc_bytes = bytes.fromhex(epc)
        epc_buffer = (c_byte * len(epc_bytes))(*epc_bytes)
        
        result = self.lib.RDR_TagConnect(self.handle, epc_buffer, len(epc_bytes))
        return result == 0

    def _disconnect_tag(self) -> bool:
        """Disconnect from the currently connected tag."""
        if not self.handle:
            return False
            
        result = self.lib.RDR_TagDisconnect(self.handle)
        return result == 0

    def read_tag_data(self, epc: str, mem_bank: int, start_word: int, word_count: int, 
                     password: int = 0) -> bytes:
        """
        Read data from a specific tag.
        Args:
            epc: Tag EPC
            mem_bank: Memory bank (0=Reserved, 1=EPC, 2=TID, 3=User)
            start_word: Starting word address
            word_count: Number of words to read
            password: Access password if required
        Returns:
            bytes: Read data or None if failed
        """
        if not self._connect_tag(epc):
            return None
            
        try:
            # Prepare parameters for tag access
            data_buffer = (c_byte * (word_count * 2))()  # *2 because each word is 2 bytes
            data_len = c_int(word_count * 2)
            
            # Configure read operation
            self.lib.RDR_ConfigBlockRead(self.handle, c_byte(mem_bank), 
                                       c_uint(start_word), c_uint(word_count))
            
            # Access tag
            result = self.lib.RDR_TagAccess(self.handle, c_uint(password))
            
            if result == 0:
                return bytes(data_buffer[:data_len.value])
            return None
            
        finally:
            self._disconnect_tag()

    def write_tag_data(self, epc: str, mem_bank: int, start_word: int, data: bytes,
                      password: int = 0) -> bool:
        """
        Write data to a specific tag.
        Args:
            epc: Tag EPC
            mem_bank: Memory bank (0=Reserved, 1=EPC, 2=TID, 3=User)
            start_word: Starting word address
            data: Data to write (bytes)
            password: Access password if required
        Returns:
            bool: True if successful
        """
        if not self._connect_tag(epc):
            return False
            
        try:
            # Prepare data buffer
            data_buffer = (c_byte * len(data))(*data)
            word_count = len(data) // 2  # Convert bytes to words
            
            # Configure write operation
            self.lib.RDR_ConfigBlockWrite(self.handle, c_byte(mem_bank),
                                        c_uint(start_word), c_uint(word_count),
                                        data_buffer, c_uint(len(data)))
            
            # Access tag
            result = self.lib.RDR_TagAccess(self.handle, c_uint(password))
            return result == 0
            
        finally:
            self._disconnect_tag()
            
    def kill_tag(self, epc: str, kill_password: int) -> bool:
        """
        Permanently disable a tag.
        Args:
            epc: Tag EPC
            kill_password: Kill password
        Returns:
            bool: True if successful
        """
        if not self._connect_tag(epc):
            return False
            
        try:
            # Configure kill operation
            self.lib.RDR_ConfigBlockLock(self.handle, c_uint(kill_password))
            
            # Access tag
            result = self.lib.RDR_TagAccess(self.handle, c_uint(kill_password))
            return result == 0
            
        finally:
            self._disconnect_tag()